# ============================================================================
# 文件路径: src/app.nim  
# 文件用途: 主应用程序入口 - Web 服务器和路由配置
#
# 功能说明:
# 1. 配置 HTTP 路由和处理器函数
# 2. 提供 Web 界面和 REST API 端点
# 3. 启动后台定时任务 (包数据更新)
# 4. 集成速率限制和 CORS 支持
# 5. 处理静态资源 (favicon、图片等)
# ============================================================================

# 导入标准库
import std/[
  json,         # JSON 数据处理
  options,      # 可选类型处理
  os,           # 文件系统操作
  strutils      # 字符串处理
]

# 导入 Web 框架
import
  mummy, mummy/routers,    # Mummy HTTP 服务器和路由
  mummy_utils,             # Mummy 工具函数
  ready                    # Redis 客户端库

# 导入项目模块
import
  ./cache,      # Redis 缓存和速率限制
  ./cron,       # 定时任务 (包数据更新)
  ./html,       # HTML 页面生成
  ./packages    # 包数据处理逻辑

# ============================================================================
# 静态资源配置 - 编译时嵌入文件内容
# ============================================================================
const favicon = staticRead("../resources/favicon.ico")    # 网站图标
const ogImage = staticRead("../resources/nimpackages.png") # Open Graph 图片

# ============================================================================
# Web 界面处理器 - 返回 HTML 页面
# ============================================================================

proc indexHandler(request: Request) =
  ## 首页处理器 - 显示所有包的列表
  let html = indexPackagesAll()
  resp(Http200, html)

proc htmlSearchHandler(request: Request) =
  ## 搜索页面处理器 - 显示搜索结果
  let query = @"q"  # 获取查询参数
  if query.len == 0:
    redirect(Http302, "/")  # 空查询重定向到首页

  let html = indexPackagesSearch(query)
  resp(Http200, html)

proc htmlPackageHandler(request: Request) =
  ## 包详情页面处理器 - 显示单个包的详细信息
  let name = @"name"  # 获取包名参数
  if name.len == 0:
    redirect(Http302, "/")  # 空包名重定向到首页

  let html = packageDetails(name)
  resp(Http200, html)

proc apiEndpointsHandler(request: Request) =
  ## API 文档页面处理器 - 显示可用的 API 端点
  let html = apiEndpoints()
  resp(Http200, html)


# ============================================================================
# REST API 处理器 - 提供 JSON 格式的数据接口
# ============================================================================

proc packagesHandler(request: Request) =
  ## API: 获取所有包的列表
  ## GET /api/packages
  var headers: HttpHeaders
  headers["Content-Type"] = "application/json"      # 设置响应类型为 JSON
  headers["Access-Control-Allow-Origin"] = "*"     # 允许跨域访问

  ratelimitHeaders(request, headers)  # 应用速率限制检查

  # 获取所有包数据并转换为 JSON
  let packages = getAllPackages()
  var packagesJson = newJArray()

  for package in packages:
    packagesJson.add(toJson(package))

  # 构建响应数据
  let response = %*{
    "packages": packagesJson,
    "count": packages.len
  }

  resp(Http200, headers, $response)

proc searchHandler(request: Request) =
  ## API: 智能搜索包 (带评分和相关性排序)
  ## GET /api/packages/search?q=<query>
  var headers: HttpHeaders
  headers["Content-Type"] = "application/json"
  headers["Access-Control-Allow-Origin"] = "*"

  ratelimitHeaders(request, headers)

  let query = @"q"
  if query.len == 0:
    resp(Http400, headers, $(%*{"error": "Query parameter 'q' is required"}))

  # 使用改进的评分搜索算法
  # 只返回高质量匹配 (评分 40+)，按相关性排序
  let searchResults = searchPackagesWithScore(query)
  var resultsJson = newJArray()

  for result in searchResults:
    resultsJson.add(toJson(result))

  let response = %*{
    "results": resultsJson,
    "count": searchResults.len,
    "query": query,
    "searchInfo": {
      "description": "High-quality results only (score 40+). Sorted by relevance: 100 = exact name, 90 = starts with, 80 = contains, 50 = description, 45 = exact tag"
    }
  }

  resp(Http200, headers, $response)

proc simpleSearchHandler(request: Request) =
  var headers: HttpHeaders
  headers["Content-Type"] = "application/json"
  headers["Access-Control-Allow-Origin"] = "*"

  ratelimitHeaders(request, headers)

  let query = request.queryParams["q"]
  if query.len == 0:
    resp(Http400, headers, $(%*{"error": "Query parameter 'q' is required"}))

  let packages = searchPackages(query)
  var packagesJson = newJArray()

  for package in packages:
    packagesJson.add(toJson(package))

  let response = %*{
    "packages": packagesJson,
    "count": packages.len,
    "query": query
  }

  resp(Http200, headers, $response)

proc tagSearchHandler(request: Request) =
  var headers: HttpHeaders
  headers["Content-Type"] = "application/json"
  headers["Access-Control-Allow-Origin"] = "*"

  ratelimitHeaders(request, headers)

  let tag = @"tag"
  if tag.len == 0:
    resp(Http400, headers, $(%*{"error": "Tag parameter is required"}))

  let packages = searchPackagesByTag(tag)
  var packagesJson = newJArray()

  for package in packages:
    packagesJson.add(toJson(package))

  let response = %*{
    "packages": packagesJson,
    "count": packages.len,
    "tag": tag
  }

  resp(Http200, headers, $response)

proc packageHandler(request: Request) =
  var headers: HttpHeaders
  headers["Content-Type"] = "application/json"
  headers["Access-Control-Allow-Origin"] = "*"

  ratelimitHeaders(request, headers)

  let name = @"name"
  if name.len == 0:
    resp(Http400, headers, $(%*{"error": "Package name is required"}))

  let package = getPackage(name)
  if package.name.len == 0:
    resp(Http404, headers, $(%*{"error": "Package not found"}))

  resp(Http200, headers, $toJson(package))

proc statsHandler(request: Request) =
  var headers: HttpHeaders
  headers["Content-Type"] = "application/json"
  headers["Access-Control-Allow-Origin"] = "*"

  ratelimitHeaders(request, headers)

  let response = %*{
    "total_packages": getPackageCount(),
    "last_updated": getLastUpdated()
  }

  resp(Http200, headers, $response)

proc faviconHandler(request: Request) =
  var headers: HttpHeaders
  headers["Content-Type"] = "image/x-icon"
  resp(Http200, headers, favicon)

proc ogImageHandler(request: Request) =
  var headers: HttpHeaders
  headers["Content-Type"] = "image/png"
  resp(Http200, headers, ogImage)

proc robotsHandler(request: Request) =
  resp(Http200, ContentType.Text, """User-agent: *
Allow: /
Sitemap: https://nimpackages.com/sitemap.xml
""")

proc sitemapHandler(request: Request) =
  var headers: HttpHeaders
  headers["Content-Type"] = "application/xml"
  resp(Http200, headers, """<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
  <url>
    <loc>https://nimpackages.com/</loc>
    <lastmod>2025-08-12T09:41:00+00:00</lastmod>
  </url>
</urlset>""")

proc statusHandlerHead(request: Request) =
  resp(Http200)

proc statusHandlerGet(request: Request) =
  resp(Http200, ContentType.Json, """{"status": "OK"}""")

proc staticBgHandler(request: Request) =
  ## 背景图片处理器 - 专门处理 /static/bg/ 路径下的图片
  let file = @"file"
  let fullPath = "static/bg/" & file
  
  # 调试信息
  echo "请求背景图片: ", file
  echo "完整路径: ", fullPath
  echo "文件是否存在: ", fileExists(fullPath)
  
  # 安全检查：防止目录遍历攻击
  if ".." in file or file.startsWith("/"):
    echo "安全检查失败: ", file
    resp(Http404, "File not found")
    return
  
  # 检查文件是否存在
  if not fileExists(fullPath):
    echo "文件不存在: ", fullPath
    resp(Http404, "File not found")
    return
  
  # 根据文件扩展名设置 Content-Type
  var headers: HttpHeaders
  let ext = splitFile(file).ext.toLowerAscii()
  case ext
  of ".jpg", ".jpeg":
    headers["Content-Type"] = "image/jpeg"
  of ".png":
    headers["Content-Type"] = "image/png"
  of ".gif":
    headers["Content-Type"] = "image/gif"
  of ".svg":
    headers["Content-Type"] = "image/svg+xml"
  of ".css":
    headers["Content-Type"] = "text/css"
  of ".js":
    headers["Content-Type"] = "application/javascript"
  of ".ico":
    headers["Content-Type"] = "image/x-icon"
  else:
    headers["Content-Type"] = "application/octet-stream"
  
  # 添加缓存头
  headers["Cache-Control"] = "public, max-age=86400"  # 缓存 24 小时
  
  # 读取并返回文件内容
  try:
    let content = readFile(fullPath)
    resp(Http200, headers, content)
  except IOError:
    resp(Http500, "Internal server error")


var router: Router
router.get("/", indexHandler)
router.get("/search", htmlSearchHandler)
router.get("/package/@name", htmlPackageHandler)
router.get("/api", apiEndpointsHandler)
router.get("/api/packages", packagesHandler)
router.get("/api/packages/search", searchHandler)
router.get("/api/packages/search/simple", simpleSearchHandler)
router.get("/api/packages/tag/@tag", tagSearchHandler)
router.get("/api/packages/@name", packageHandler)
router.get("/api/stats", statsHandler)
router.get("/favicon.ico", faviconHandler)
router.get("/nimpackages.png", ogImageHandler)
router.get("/robots.txt", robotsHandler)
router.get("/sitemap.xml", sitemapHandler)
router.get("/static/bg/@file", staticBgHandler)

router.head("/", statusHandlerHead)
router.get("/status", statusHandlerGet)

var thread: Thread[void]
var githubThread: Thread[void]

proc main() =
  # ====================================================================
  # 启动信息和模式检测
  # ====================================================================
  echo "🌸 风铃草 (Campanula) 包管理器启动中..."
  
  when defined(dev):
    echo "🧪 运行模式: 开发模式"
    echo "   - 使用本地缓存数据"
    echo "   - 限制仓库信息更新"
    echo "   - 启用调试输出"
  elif defined(release):
    echo "🚀 运行模式: 发布模式"
    echo "   - 从官方源获取数据"
    echo "   - 完整功能启用"
  else:
    echo "⚙️  运行模式: 默认模式 (开发行为)"
    echo "   - 使用本地缓存数据"
    echo "   - 限制后台任务"
    echo "💡 提示: 使用 'nim c -d:release src/app.nim' 启用发布模式"

  # ====================================================================
  # Redis 连接检查和初始化
  # ====================================================================
  echo "🔍 检查 Redis 连接..."
  var redisAvailable = false
  try:
    cachePool.withConnection conn:
      let response = conn.command("PING")
      if response.to(string) == "PONG":
        redisAvailable = true
        echo "✅ Redis 连接正常"
        
        # 检查是否有包数据
        let count = conn.command("GET", "packages_count")
        let countOpt = count.to(Option[int])
        if countOpt.isSome and countOpt.get > 0:
          echo "📦 Redis 中已有 ", countOpt.get, " 个包的数据"
        else:
          echo "📦 Redis 中暂无包数据，后台任务将自动获取"
      else:
        echo "⚠️  Redis 响应异常"
  except CatchableError as e:
    echo "❌ Redis 连接失败: ", e.msg
    echo "💡 提示: 请确保 Redis 服务器正在运行"
    echo "🔧 常见解决方案:"
    echo "   • Docker: docker run -d -p 6379:6379 redis:alpine"
    echo "   • Ubuntu/Debian: sudo apt install redis-server && sudo systemctl start redis"
    echo "   • CentOS/RHEL: sudo yum install redis && sudo systemctl start redis"
    echo "   • macOS: brew install redis && brew services start redis"
    echo ""
    echo "⚠️  应用将继续启动，但数据功能将受限"
  
  if not redisAvailable:
    echo "🔄 将在后台持续尝试连接 Redis..."

  # ====================================================================
  # 启动后台定时任务
  # ====================================================================
  echo "🔄 启动后台定时任务..."
  createThread(thread, updatePackages)
  createThread(githubThread, updatePackagesWithRepoInfo)

  # ====================================================================
  # 启动 Web 服务器
  # ====================================================================
  let server = newServer(router)
  echo "✅ 服务器启动成功！"
  echo "🌐 访问地址: http://localhost:8080"
  echo "📚 API 文档: http://localhost:8080/api"
  echo ""
  echo "按 Ctrl+C 停止服务器"
  
  server.serve(Port(8080))

when isMainModule:
  main()